Portfolio Monitor ability#271
Conversation
🔀 Branch Merge CheckPR direction: ✅ Passed — |
✅ Community PR Path Check — PassedAll changed files are inside the |
✅ Ability Validation Passed |
🔍 Lint Results✅
|
There was a problem hiding this comment.
Portfolio Monitor — Issues to Address
Tested end-to-end. The architecture (interactive skill + background daemon) is solid, but a handful of issues need addressing before merge. Listed roughly by impact:
-
One-shot handler + narrow
does_matchlets the agent LLM hallucinate ability behavior._run()handles a single intent then callsresume_normal_flow(), so follow-ups like"add stock","google","also add nvidia"don't re-passdoes_matchand the agent LLM invents fake confirmations ("Great choice! Google is now in your portfolio") while no state changes — daemon logs show0 holding(s)throughout. Fix: expandHOTWORDS, drop the"stock" not in texclusion in theaddregex, and make_handle_add/_handle_set_alertconversational with explicit-exit loops. Pattern reference:official/audius-music-dj. -
File I/O doesn't persist across triggers.
_save_datadoesdelete_file → write_filewith a bareexcept— any silent failure loses the file. Reproduced: added stocks, retriggered, "No stocks yet." Switch to Context Storage (get_single_key/create_key/update_key) — same APIaudius-music-djuses forfavoritesandglobal_context. Docs: capability-worker.md. -
Hardcoded API keys (
FINNHUB_KEY = "your_finnhub_api_key"etc.) with README telling users to editmain.py. Use the SDK pattern — single-key call returning the value directly:self.finnhub_key = self.capability_worker.get_api_keys("finnhub_api_key") or "" self.av_key = self.capability_worker.get_api_keys("alphavantage_api_key") or ""
Add an upfront check in
_run()that speaks a clear message when no keys are configured.
Docs reference: - https://docs.openhome.com/building-abilities/how-to-build#custom-api-keys-third-party-services -
Silent correctness bugs:
main.py:387—(position_pnl / position_cost * 100) if position_cost else 0is a dead expression; per-stock P&L % is never spoken.main.py:470—pos_pnl / pos_valueshould bepos_pnl / pos_cost. A stock that doubled reports "50%" instead of "100%"._handle_set_alertresets the per-ticker dict to{}before setting either threshold — setting drop and rise in separate sessions loses the earlier one. Usesetdefault(ticker, {})+is not Nonechecks.does_matchcalls_resolve_tickerwhich falls through to_resolve_ticker_llm→text_to_text_responsefires on every utterance, burning LLM quota.does_matchmust stay synchronous and cheap.
-
README claims that don't hold up:
- Daemon caps at 10 holdings (
MAX_API_CALLS_PER_POLL) — stocks past index 10 never refresh or alert. _handle_portfolioonly fetches when ticker is absent from cache — stale entries are never refreshed during a session.- EOD summary reads from the cache; misses closing-bell moves. Force a fresh fetch in the EOD window.
_handle_checkskips saving cache for non-portfolio stocks — re-hits API every time.
- Daemon caps at 10 holdings (
Let's bring this ability into full compliance with the SDK and voice UX requirements. Once fixed, we'd like to consider it for marketplace release — please update main.py and background.py accordingly to the best of your knowledge and best abilities practices.
062edda to
13731a6
Compare
|
Hey @uzair401 - appreciate the thorough review, really helpful catching these before merge. Went through everything you flagged and pushed fixes for all of it: The LLM hallucination issue was the biggest one - Switched the whole persistence layer over to Context Storage ( API keys are now loaded at runtime via Fixed all four of the silent correctness bugs - the dead P&L expression, the On the daemon side: EOD summary now forces a fresh fetch before computing totals, Let me know if anything else needs attention. |
uzair401
left a comment
There was a problem hiding this comment.
Hello @hassan1731996,
This is a great ability and your contribution is much appreciated — I'd like to push it a bit further so it's marketplace-ready. A few directions I'd like us to take it:
Make it a one-stop place for stocks. Beyond just looking things up, it should also track and monitor the user's portfolio, provide live market insights, show current stock status, and do a basic day-over-day comparison (no need to go deeper than that for now).
Replace regex-based routing with an LLM intent router. Regex routing tends to be fragile for an ability this complex. Using the LLM as the intent router will route user requests much more reliably.
Restructure main.py to be more feature-centric. When the ability is triggered, the user should be able to check their portfolio, check current stock status, and view the previous day's comparison — all from one entry point.
Tighten intent handling. The check / move (or change) / delete flows need to work properly and consistently through the LLM router.
Once these are in, we'll do rigorous testing before submitting for marketplace approval. Thanks again for the work on this!
|
Hey @uzair401 - pushed another round of changes based on your second review. LLM intent router is in. Day-over-day compare is a proper feature now. Saying "compare my stocks", "day over day", or "versus yesterday" triggers Feature-centric entry point - Let me know how testing goes. |
uzair401
left a comment
There was a problem hiding this comment.
Thanks @hassan1731996 for the quick turnaround on the requested changes. There are still a few improvements that need to be made, and I'll keep the PR open until those changes are in. I'll work through these, and once they're finalized by our reviewer and QA team, I'll share an update along with the revised code and updated README. After that, we'll proceed with marketplace approval. Thanks again for contributing to OpenHome!
Hey @uzair401 - glad the changes landed well. Happy to keep iterating if there's anything specific you want me to adjust on my end in the meantime. Looking forward to seeing what the QA pass surfaces - and appreciate you taking it the rest of the way to marketplace. Exciting milestone for this one. |
|
Few more additions in this push: UPDATE intent - full position management in one flow: bought more shares (recalculates weighted average cost), sold some (reduces share count, or removes the stock entirely if you sell all of it with confirmation), and correct/overwrite if the numbers are just wrong. Reachable directly via voice ("I bought more Tesla", "sold some Apple") or from within the ADD flow when a stock already exists. MARKET intent - "how are the markets" pulls live S&P 500, Nasdaq, and Dow Jones from the same Finnhub feed. Works both as a standalone trigger and as a navigation option inside the portfolio hub. Hub navigation → LLM sub-router - unified the portfolio dashboard's follow-up loop with the same LLM routing approach used at the top level. Both now go through the same pattern so phrasing is handled consistently throughout. Chunked voice output - BREAKDOWN and COMPARE speak 4 stocks at a time and ask "Want to hear the rest?" before continuing. Keeps things natural for larger portfolios instead of reading everything out at once. README example conversation - added a full walkthrough: portfolio hub, market check, per-stock query, breakdown pagination, position update, and a background alert firing. Let me know if you have any other thoughts. |
d7d0918 to
4bf04ff
Compare
4bf04ff to
bb1122e
Compare
Portfolio Monitor
A passive background ability that tracks your stock portfolio in real time, fires proactive alerts when positions move beyond your thresholds, and delivers a full P&L breakdown on demand — all by voice.
What's in this PR
Core features
Interactive intents
PORTFOLIO— snapshot (value, day P&L, overall P&L) → hub navigationCHECK— specific stock with position P&L, follow-up loopCOMPARE— day-over-day vs yesterday's close, chunked at 4 stocksMOVERS— biggest gainer and loser in your portfolioMARKET— live S&P 500, Nasdaq, Dow Jones pulseADD— one-shot or prompted; offers UPDATE if stock already existsUPDATE— bought more (weighted avg cost recalc), sold some (share reduction or full remove), correct/overwriteSET_ALERT— drop/rise % thresholds per stock, loop for multipleREMOVE— with confirmationCLEAR— with confirmationVoice UX
Setup
What changed since initial review
COMPAREintent with day-over-day viewUPDATEintent (bought more / sold some / correct) — handles all position change flowsMARKETintent — S&P 500, Nasdaq, Dow via same Finnhub feed_classify_hub_action) for consistencyADDflow no longer dead-ends on existing stock — offers UPDATE insteaddoes_matchis fully synchronous — no LLM, no I/Oresume_normal_flowinfinallyblock, daemon startup onlyget_api_keys()at runtime, not hardcodedFiles
community/portfolio-monitor/main.py— foreground skillcommunity/portfolio-monitor/background.py— background daemoncommunity/portfolio-monitor/README.md— setup, trigger phrases, example conversation